/*
 * ===================================================================
 *  
 *      Filename:  log_client_main_framer.c
 *
 *   Description:  This file is the main routine for the msg output.
 *                 The customed msg will be formated in the main framer,
 *                 and then send to the daemon thread.
 *
 *       Version:  1.0
 *        Create:  2010/07/13
 *
 *        Author:  Keven Zhu     kevenzhu@tencent.com
 *     Copyright:  Reserved by Tencent.com Co.ltd
 *          
 * ====================================================================
 */
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <fcntl.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/wait.h>
#include <pthread.h>

#include "lib/util.h"
#include "log_client_queue.h"
#include "log_client_api.h"

#define LOCKFILE "/var/run/hplog.pid"   /*file lock which used to keep the single instance*/
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)

static char log_level_map[][LEVEL_MAX] = 
{
    "[DEBG]",
    "[INFO]",
    "[NOTC]",
    "[WARN]",
    "[EROR]",
    "[CRIT]",
    "[ALET]",
    "[EMGT]"
};

#if IPC_SHM
char* shamm_client_addr = NULL;
int   shamm_client_id;
local_share_mem_ctl_header local_client_ctl_header;
#endif
#if IPC_MSG
int   msg_queue_client  = INV_HANDLER;
struct log_msg
{
    long msg_type;
    char text[MAX_LOG_MSG_SIZE];
};
#endif

log_client_conf local_log_conf;
char  local_tmp_buff[MAX_LOG_MSG_SIZE];
int   local_log_fd = INV_HANDLER;

/*add the mutex to protect the file handler*/
pthread_mutex_t file_mutex ;

void handle_log_level_signal(int signum,siginfo_t* info, void* myact)
{
    set_client_log_level(info->si_value.sival_int);
}

void recycle_zombie(int num)
{
    pid_t child;
    int status;
    do {
        child = waitpid(-1, &status, WNOHANG);
    }while(child > 0);
}

/* initialize the log modules
 * @log_conf: the configuration for the log, please make sure the unset fields are placed with zero
 * 
 * return value:
 * positive means failure,error could be found in the head file or with the help of plogerr();
 * 0 means ok*/
int init_hplog(log_client_conf *log_conf)
{
    #if IPC_SHM
    key_t key = (key_t)HPLOG_SHMM_KEY;
    share_mem_ctl_header *tmp = NULL;
    int iret = 0;
    #endif	
    struct sigaction act;

    if(!log_conf)
    {
        return NOCNF;
    }
    memcpy(&local_log_conf,log_conf,sizeof(log_client_conf));

    /*detect the max local file size*/
    if(!(local_log_conf.max_log_size))
    {
        local_log_conf.max_log_size = MAX_LOG_SIZE;
    }

    /*init the log doctor*/
    init_log_doctor();
    
    /*mornitor the child*/
    signal(SIGCHLD,recycle_zombie);

    log_doctor(doctor_level,"try to detect the daemon\n");
    if(load_hplog_client_deamon())
    {
        return FLDON;
    }
    #if IPC_MSG	
    /*the share memory solution is abandoned,we will msg queue instead*/
    msg_queue_client = msgget((key_t)HPLOG_MSG_QUEUE,0666|IPC_CREAT);
    if(msg_queue_client == INV_HANDLER)
    {
        log_doctor(doctor_level,"failed to initilize the msg queue\n");
        return INVIO;
    }
    #endif    
    #if IPC_SHM
    log_doctor(doctor_level,"try to get the share memory handler\n");
    /*get the share memory handler and map to our process*/
    shamm_client_id = shmget(key,0,0);
    if(-1 == shamm_client_id)
    {
        return FGTHL;
    }

    shamm_client_addr = shmat(shamm_client_id,0,0);
    if(-1 == (long)shamm_client_addr)
    {
        /*reset the memory to NULL*/
        shamm_client_addr = NULL;
        return FMPMM;
    }
    /*set the memory of control to zero*/
    tmp = (share_mem_ctl_header*)shamm_client_addr;
    init_queue(tmp,MAX_LOG_MSG_COUNTER);
    /*map the share memory ctl header to our local process*/
    memset((void*)&local_client_ctl_header,0,sizeof(local_client_ctl_header));
    local_client_ctl_header.max_queue          = &(tmp->max_queue);
    local_client_ctl_header.max_queue_mask     = &(tmp->max_queue_mask);
    local_client_ctl_header.enqueue_counter    = &(tmp->enqueue_counter);
    local_client_ctl_header.dequeue_counter    = &(tmp->dequeue_counter);
    while(iret<MAX_LOG_MSG_COUNTER)
    {
        local_client_ctl_header.log_list[iret] = (char*)(shamm_client_addr + sizeof(share_mem_ctl_header) + MAX_LOG_MSG_SIZE*iret);
        iret ++;
    }

    log_doctor(doctor_level,"try to initialize the mutex\n");
    /*get the semphore for the shared memory*/
    if(INVIO == initialize_hplog_mutex_sem(CLIENT_PROCESS))
    {
        return INVIO;
    }
    #endif

    log_doctor(doctor_level,"create the file mutex\n");
    /*create the pthread_mutex*/
    if(pthread_mutex_init(&file_mutex,NULL))
    {
        perror("failed to initialzie the file mutex");
        return INVIO;
    }   

    /*if the local flag is set,create the local storage*/
    if(local_log_conf.flags&LOG_MSG_STORE_LOCAL)
    {
        /*we need to write the file to local storage*/
        if(!(*(local_log_conf.local_log_path)))
        {
            strcpy(local_log_conf.local_log_path,"default.log");
        }
        if(-1 == (local_log_fd = open(local_log_conf.local_log_path,O_CREAT|O_RDWR,00666)))
        {
            perror("failed to open the file");
            return INVIO;
        }
        /*move the file handler to the end of the file*/
        pthread_mutex_lock(&file_mutex);
        lseek(local_log_fd,0,SEEK_END);
        pthread_mutex_unlock(&file_mutex);

    }
    /*install the singal for the log level*/
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;
    act.sa_sigaction = handle_log_level_signal;
    if(sigaction((SIGRTMIN+8),&act,NULL)<0)
    {
        log_doctor(doctor_level,"error to initialze the log level signal\n");
        return INVIO;
    }
    return NOERR;
}

/* destroy the log modules
 * 
 * return value:
 * positive means failure,error could be found in the head file or with the help of plogerr();
 * 0 means ok*/
int finit_hplog()
{
    log_doctor(doctor_level,"The daemon can NOT be terminated here,ask kevenzhu for trick\n");
    return NOERR;
}

/* set client log level in the running time*/
inline int set_client_log_level(int log_level)
{
    if(log_level <0 ||log_level >8)
    {
        return INVIO;
    }
    else
    {
        local_log_conf.log_severity = log_level;
        return NOERR;
    }
}

/* return the total length of the log*/
int format_string(int log_level,char* buff,const char* fmt,va_list args)
{
    #define  LOG_RESERVED_LEN  3  //length(2 bytes) + color(1 byte)
    #define  LOG_DATE_LEN      21
    #define  LOG_LEVEL_LEN     6   
    struct tm result;
    time_t tr;
    static unsigned short msg_seq = 0;
    int    iret = 0,ilen = 0;
    
    iret = iret + LOG_RESERVED_LEN;
  
    /*set the color to zero*/
    buff[2] = '\0';
    /*add the date to the log msg*/
    tr = time(NULL);
    localtime_r(&tr,&result);
    sprintf( buff+iret, "[%4d/%02d/%02d %02d:%02d:%02d]",
                        result.tm_year+1900, result.tm_mon,
                        result.tm_mday, result.tm_hour,
                        result.tm_min,  result.tm_sec);
    iret = iret + LOG_DATE_LEN ;
    
    /*add the log level to the msg*/
    memcpy(buff+iret,log_level_map[log_level],LOG_LEVEL_LEN);
    iret = iret + LOG_LEVEL_LEN;
 
    /*add the process name to the log msg*/
    if(local_log_conf.process_name[0])
    {
        ilen = strlen(local_log_conf.process_name);
        sprintf(buff+iret,"[%s]",local_log_conf.process_name);
        iret = iret + ilen + 2;
    }
    /*add the seq to the msg*/
    sprintf(buff+iret,"[S:0x%04x]",msg_seq++);
    iret = iret + 10;/*10 is the len of the pid string*/

    /*add the pid to the header*/
    sprintf(buff+iret,"[P:0x%08x]",getpid());
    iret = iret + 14;/*14 is the len of the pid string*/

    /*add the : to seprate content from the header*/
    buff[iret] = ':';
    iret = iret + 1;/*1 byte for the space*/
    
    /*copy the date to the log msg,we must keep the header away!!*/
    ilen = vsnprintf(buff+iret,(MAX_LOG_MSG_SIZE-iret-5),fmt,args);
    iret = iret + ilen;
    /*add the termination flag to the string*/
    if(buff[iret-1] != '\n')
    {
        buff[iret]   = '\n';
        buff[iret+1] = '\0';
        iret = iret + 2;
    }
    else
    {
        buff[iret] = '\0';
        iret = iret + 1;
    }

    /*add the log level to the end of the buff*/
    *((char*)(buff+iret)) = log_level;
    iret = iret + 1;

    /*set the string len to the header*/
    *((short*)buff) = iret;
    /*finish the job, return the additional length*/
    return iret;
}

/* compare the file with the conf file, if exceed than the quote,return to the begin*/
void relocate_file_point(int fd)
{
    struct stat file_des;

    /*we didn't define max_log_size*/
    if(!(local_log_conf.max_log_size))
    {
        return;
    }
    if(-1 == fstat(fd,&file_des))
        return;

    if(file_des.st_size >= local_log_conf.max_log_size)
    {
        pthread_mutex_lock(&file_mutex);
        lseek(fd,0L,SEEK_SET);
        pthread_mutex_unlock(&file_mutex);
    }
}

#define MAX_WRITE_COUNTER  1000
/* if continue to send the packet to remote side, 0 return
 * otherwise,stop to process the packet*/
int detect_flags(char* buff)
{
    int    iret = 0;
    static int local_write_counter = 0;

    /*if the handler is validate,we assume that the local storage is enabled*/
    if(INV_HANDLER != local_log_fd)
    {
        pthread_mutex_lock(&file_mutex);
        iret =  write(local_log_fd,buff+3,(*((short*)buff))-4);
        pthread_mutex_unlock(&file_mutex);
        if(-1 == iret)
        {
            relocate_file_point(local_log_fd);
            perror("failed to write data to the local file");
        }
        else         
        {
            if(local_write_counter++ > MAX_WRITE_COUNTER)
            {
                relocate_file_point(local_log_fd);
                local_write_counter = 0;
            }
        }
        
    }
    /*if the monitor is on*/
    if(local_log_conf.flags&LOG_MSG_STORE_MONITOR)
    {
        printf("%s",buff+3);
    }

    /*do we need to send it to the server*/
    if(local_log_conf.flags&LOG_MSG_STORE_REMOTE)
        return NOERR;
    else
        return FLOG;
}
/*main interface for the log output*/
int hplog_output(int log_level,const char* fmt,...)
{
    /*keep the function running in the multithread*/
    #if IPC_SHM
	char   buff[MAX_LOG_MSG_SIZE];
    #endif
    #if IPC_MSG			
    struct log_msg log_data;
    char   *buff = log_data.text;
    log_data.msg_type = 1;
    #endif	
    
    #if IPC_SHM
    if(!shamm_client_addr)
    {
        log_doctor(doctor_level,"We must complain that the client was NOT initialzied\n");
        return NITCL;
    }
    #endif


    /*test the log level*/
    if(log_level < local_log_conf.log_severity)
        return NOERR;

    /*format the string*/
    va_list args;
    va_start( args, fmt );
    format_string(log_level, buff, fmt, args);
    va_end( args );

    /*we convert the string to our private format,now check the flag*/
    if(detect_flags(buff))
        return FLOG;

    /*send the string to the destination*/
    #if IPC_MSG	
    //if(msgsnd(msg_queue_client,(void*)&log_data,MAX_LOG_MSG_SIZE,IPC_NOWAIT) == -1)
    if(msgsnd(msg_queue_client,(void*)&log_data,MAX_LOG_MSG_SIZE,0) == -1)
    {
        /*if we failed to send the msg to the queue,we assume the daemon is down, restart it*/
        if(load_hplog_client_deamon())
        {
            log_doctor(doctor_level,"failed to initilize the log daemon\n");
            return FLDON;
        }
        /*the share memory solution is abandoned,we will msg queue instead*/
        msg_queue_client = msgget((key_t)HPLOG_MSG_QUEUE,0666|IPC_CREAT);
        if(msg_queue_client == INV_HANDLER)
        {
            log_doctor(doctor_level,"failed to initilize the msg queue\n");
            return INVIO;
        }
        log_doctor(doctor_level,"daemon is down,restart it,but failed to send the msg to the queue: %s",buff+3);
    }
    #endif	
    #if IPC_SHM
    hplog_sem_writer_wait();
    enqueue(&local_client_ctl_header,buff);
    hplog_sem_reader_post();
    #endif
    return NOERR;
}

/*define the error number*/
typedef struct err_map_
{
    char* name;
    int   number;
}err_map;
static err_map log_error_map[] = 
{
    {"No error",0},
    {"Out of memory",1},
    {"Invalid file handler",2},
    {"No disk space",3},
    {"No server is available",4},
    {"Operation not permitted",5},
    {"Socket time out",6},
    {"Socket reset by the peer",7},
    {"Socket rejected by the peer",8},
    {"No configuration file found",9},
    {"Failed to open the lock file",10},
    {"Failed to lock the lock file",11},
    {"Failed to get the share memory handler",12},
    {"Failed to initialize the client daemon",13},
    {"Failed to initialize the share memory",14},
    {"Failed to map the share memory to the process",15},
    {"Failed to assosiate the sem body with the sem id",16},
    {"Error to release the semphone",17},
    {"Failure,queue is full",18},
    {"Failure,daeon is running",19},
    {"The client was NOT initialized",20},
    {"Finished the logical,return",21},
    {"Failed to update the policy file",22},
    {NULL,-1}
};
/* format the error msg according to the error number*/
char* plogerr(int errno)
{
    if((errno > (sizeof(log_error_map)/sizeof(log_error_map[0]))) || (errno<0))
        return NULL;
    else
        return log_error_map[errno].name;
}

